#!/usr/bin/env python
"""
Acorn-SDR (A Collaboration of Radio Nodes)
(c) Bob Cowdery (G3UKB) 2009

Date: July 2009

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

The author can be reached by email at

bob@g3ukb.co.uk

========================================================================
Module Description:

A meters implementation using GTK+.

"""

#-----------------------------------------------------------------------
# System imports
import sys
from time import sleep
import subprocess
import string
from scipy import *
from scipy.signal.signaltools import resample

# Import GTK libraries
try:
  import pygtk
  #tell pyGTK, if possible, that we want GTKv2
  pygtk.require("2.0")
except:
  #Some distributions come with GTK2, but not pyGTK
  pass
try:
  import gtk
  import gtk.glade
  import gobject
  import pango
except:
  print "You need to install pyGTK or GTKv2 ",
  print "or set your PYTHONPATH correctly."
  print "try: export PYTHONPATH=",
  print "/usr/local/lib/python2.2/site-packages/"
  sys.exit(1)

# Application imports
import utils
from dttsp_map import *

#======================================================================
# The rx meters class
class AppMeters:
  
    #---------------------------------------------------------------------
    def __init__(self, main_tree, persist_service, ice_config):
        # Stash parameters
        self.main_tree = main_tree
        self.persist = persist_service
        self.ice_config = ice_config
        
        # Initialise variables
        self.pixmap = None
        self.meter_data = None
        self.config = False
        self.rx_meter_mode = None
        
        # ToDo this needs to be commoned up
        # Load the main window
        #mainwindow = 'Displays'
        # This loads a widget tree from the XML definition in the glade file
        #self.main_tree=gtk.glade.XML (glade_path, mainwindow)
        
        # Get the main window
        #w=self.main_tree.get_widget(mainwindow)
        #w.show_all()

        # Wire up the top level window events
        # All events are set up in Glade, just match the event names here
        dic = {
                "on_da_meter_configure_event" : self.config_event,
                "on_da_meter_expose_event" : self.expose_event,
                "on_cb_rxmeter_changed" : self.rxmeter_changed,
            }
        
        # This will connect up all the events in one hit
        self.main_tree.signal_autoconnect (dic)
        
        # Now we need a reference to all the controls
        # All controls in the tree must have a unique name
        self.dameter = self.main_tree.get_widget('da_meter')
        self.ldbm = self.main_tree.get_widget('ldbm')
        self.cbrxmeter = self.main_tree.get_widget('cb_rxmeter')
        self.cbtxmeter = self.main_tree.get_widget('cb_txmeter')
        
        # Create the initial pixmap (as contrary to popular belief config_event does
        # not get called when the window is created)
        x, y, width, height = self.dameter.get_allocation()
        self.pixmap = gtk.gdk.Pixmap(self.dameter.window, width, height)
        # Pango layout for text
        self.layout1 = self.dameter.create_pango_layout("")        
        self.layout1.set_width(25 * pango.SCALE)
        attrList = pango.AttrList()
        attribute = pango.AttrForeground(65535, 42075, 0, 0, -1)
        attrList.insert(attribute)
        self.layout1.set_attributes(attrList)
        self.layout2 = self.dameter.create_pango_layout("")        
        self.layout2.set_width(25 * pango.SCALE)
        attrList = pango.AttrList()
        attribute = pango.AttrForeground(65535, 0, 0, 0, -1)
        attrList.insert(attribute)
        self.layout2.set_attributes(attrList)
        self.layout3 = self.dameter.create_pango_layout("")        
        self.layout3.set_width(25 * pango.SCALE)
        attrList = pango.AttrList()
        attribute = pango.AttrForeground(65535, 65535, 65535, 0, -1)
        attrList.insert(attribute)
        self.layout3.set_attributes(attrList)
        
        # Create gc's for drawing
        colormap = self.dameter.get_colormap()
        white = colormap.alloc_color('white', True, True)
        self.white_gc = self.pixmap.new_gc(foreground=white, line_width=1)
        red = colormap.alloc_color('red', True, True)
        self.red_gc = self.pixmap.new_gc(foreground=red, line_width=1)
        orange = colormap.alloc_color('orange', True, True)
        self.orange_gc = self.pixmap.new_gc(foreground=orange, line_width=1)
        yellow = colormap.alloc_color('yellow', True, True)
        self.yellow_gc = self.pixmap.new_gc(foreground=yellow, line_width=1)
        
        # Init size dependent data
        self.init_data(width, height)
        
        # Avoid circular events
        # One issue with messaging is that a lot of controls generate an event when they
        # are changed programmatically. So you hit a button, it sends a message, then a 
        # message comes back that causes the button state to change which causes it to 
        # generate an event ... 
        self.ignore = False
        
        # Widget map
        self.rx_meter_modes = {'RX_SIGNAL_STRENGTH':(0, 'Signal Strength'),'RX_AVG_SIGNAL_STRENGTH':(1, 'Av Signal Strength'),'RX_ADC_REAL':(2, 'ADC Real'),'RX_ADC_IMAG':(3, 'ADC Imag'),'RX_AGC_GAIN':(4, 'AGC Gain')}
        # ToDo add later
        tx_meter_modes = None
        
        # Finally, we need to set our GUI according to capabilities
        cap_rx_meter = utils.persist_get(self.persist, 'Capability', 'rx_meter')
        if cap_rx_meter <> 'Empty':
            # Adjust rx for capabilities
            liststore = gtk.ListStore(gobject.TYPE_STRING)
            self.cbrxmeter.set_model(liststore)
            cell = gtk.CellRendererText()
            self.cbrxmeter.pack_start(cell, True) 
            allowed_rx_meter_modes = cap_rx_meter.rsplit(',')
            for allowed_rx_meter_mode in allowed_rx_meter_modes:
                if allowed_rx_meter_mode in self.rx_meter_modes.keys():
                    # Populate the dropdown
                    self.cbrxmeter.append_text(self.rx_meter_modes[allowed_rx_meter_mode][1])
            # For now until stored
            self.cbrxmeter.set_active(0)
        else:
            self.cbrxmeter.set_sensitive(False)
            
        cap_tx_meter = utils.persist_get(self.persist, 'Capability', 'tx_meter')
        if cap_tx_meter <> 'Empty':
            # Adjust rx for capabilities
            liststore = gtk.ListStore(gobject.TYPE_STRING)
            self.cbtxmeter.set_model(liststore)
            cell = gtk.CellRendererText()
            self.cbtxmeter.pack_start(cell, True) 
            allowed_tx_meter_modes = cap_tx_meter.rsplit(',')
            for allowed_tx_meter_mode in allowed_tx_meter_modes:
                if allowed_tx_meter_mode in tx_meter_modes.keys():
                    # Populate the dropdown
                    self.cbtxmeter.append_text(tx_meter_modes[allowed_tx_meter_mode][1])
        else:
            self.cbtxmeter.set_sensitive(False)

    #---------------------------------------------------------------------
    # GUI generated events (i.e. things that happen when you play with widgets)
    
    #=====================================================================
    # Events for the drawing area
    #---------------------------------------------------------------------
    # Called on window resize
    # Create a pixmap for the current window size
    # This seems to always be followed by an expose event, so let that do the drawing
    def config_event(self, widget, event):
        self.config = True
        x, y, width, height = widget.get_allocation()
        self.pixmap = gtk.gdk.Pixmap(widget.window, width, height)
        self.clear_pixmap(widget, width, height)
        self.init_data(width, height)
        self.config = False

    #---------------------------------------------------------------------
    # Called when the window needs redrawing
    # Can be forced by calling queue_draw()
    # Redraw the screen from the backing pixmap
    def expose_event(self, widget, event):
        if self.pixmap:
            x , y, width, height = event.area
            self.clear_pixmap(widget, width, height)
            self.draw_static(widget, width, height)
            if self.meter_data:
                self.draw_dynamic(widget, width, height, self.meter_data)
            # Refresh the display
            widget.window.draw_drawable(widget.get_style().fg_gc[gtk.STATE_NORMAL], self.pixmap, x, y, x, y, width, height)
        return False
    
    #=====================================================================
    # Events for the meter dropdowns
    #---------------------------------------------------------------------
    def rxmeter_changed(self, widget):
        # Remember the selected mode
        self.rx_meter_mode = self.get_active_text(widget)
        
    #---------------------------------------------------------------------
    # Interface
    #---------------------------------------------------------------------
    #---------------------------------------------------------------------
    # Meter data event
    def thd_meter(self, meter_data):
        gobject.idle_add(self.meter, meter_data)
        
    def meter(self, meter_data):
        x, y, width, height = self.dameter.get_allocation()
        self.meter_data = meter_data
        self.clear_pixmap(self.dameter, width, height)
        self.draw_static(self.dameter, width, height)
        self.draw_dynamic(self.dameter, width, height, meter_data)
        # Request a repaint
        self.dameter.queue_draw()
        
    #=====================================================================
    # Private
    #---------------------------------------------------------------------
    def get_active_text(self, combobox):
        model = combobox.get_model()
        active = combobox.get_active()
        if active < 0:
            return None
        else:
            for item in self.rx_meter_modes.keys():
                if self.rx_meter_modes[item][1] == model[active][0]:
                    return self.rx_meter_modes[item][0]
        
    #---------------------------------------------------------------------       
    def init_data(self, width, height):
        # Signal Strength
        # We want a scale of 120db (S0-S9 = 10*6db + 0-60 over 9 = 60 + 60 db)
        # Leave a margin of 2 pixels are each end
        MARGIN = 2
        self.MARGIN = MARGIN
        MAX_DB = 120.0
        SMALL_TICK = 3
        LARGE_TICK = 6
        S_POINT = 6.0
        S_POINT_TICKS = 9
        OVER_NINE_TICKS = 7
        OVER_NINE_INC = 10.0
        available_width = width - (MARGIN*2)
        self.db_per_pixel = float(available_width)/ MAX_DB
        self.s_scale = [[],[]]
        # Set up the axis drawing spec
        self.s_scale[0].append((self.white_gc, MARGIN, height-MARGIN, int((S_POINT_TICKS*S_POINT*self.db_per_pixel))-MARGIN, height-MARGIN))
        self.s_scale[0].append((self.red_gc, int((S_POINT_TICKS*S_POINT*self.db_per_pixel))-MARGIN, height-MARGIN, width-MARGIN, height-MARGIN))
        # Set up the rest of the drawing spec
        tick_at = MARGIN
        this_tick = SMALL_TICK
        which_tick = 0
        self.s_scale[1].append((self.white_gc, tick_at, this_tick, (self.layout3, '', tick_at-3, 3)))
        for n in range(S_POINT_TICKS):
            which_tick += 1
            if which_tick%2==0: this_tick = SMALL_TICK
            else: this_tick = LARGE_TICK
            tick_at = tick_at + int(S_POINT*self.db_per_pixel)
            if this_tick == LARGE_TICK:
                self.s_scale[1].append((self.white_gc, tick_at, this_tick, (self.layout3, str(n+1), tick_at-3, 3)))
            else:
                self.s_scale[1].append((self.white_gc, tick_at, this_tick, (self.layout3, '', tick_at-3, 3)))
            
        for n in range(OVER_NINE_TICKS):
            which_tick += 1
            if which_tick%2==0: this_tick = SMALL_TICK
            else: this_tick = LARGE_TICK
            tick_at = tick_at + int(OVER_NINE_INC*self.db_per_pixel)
            if this_tick == LARGE_TICK:
                self.s_scale[1].append((self.red_gc, tick_at, this_tick, (self.layout2, '+'+str((n+1)*10), tick_at-11, 3)))
            else:
                self.s_scale[1].append((self.red_gc, tick_at, this_tick, (self.layout2, '', tick_at-11, 3)))
        
    #---------------------------------------------------------------------       
    def draw_static(self, widget, width, height):
        if self.config: return
        # Draw the base line
        for item in self.s_scale[0]:
            gc, x, y, x1, y1 = item
            self.pixmap.draw_line(gc, x, y, x1, y1)
        # Draw remainder of control
        for item in self.s_scale[1]:
            lgc, xs, tick_height, text_spec = item
            layout, text, tx, ty = text_spec
            self.pixmap.draw_line(lgc, xs, y, xs, height-tick_height)
            layout.set_text(text)
            self.pixmap.draw_layout(lgc, tx, ty, layout)
    
    #---------------------------------------------------------------------
    def draw_dynamic(self, widget, width, height, meter_data):
        if self.config: return
        # The S9 value is taken to be -73dBm
        # The start of our scale is therefore -127dBm
        # Assume calibration is -40 for now
        CORRECTION = -40
        db_offset = 127-abs(meter_data[self.rx_meter_mode] + CORRECTION)
        if db_offset  > 0:
            # Just draw a vertical line at the db point
            x = self.MARGIN + int(self.db_per_pixel*db_offset)
            self.pixmap.draw_line(self.yellow_gc, x, height-self.MARGIN, x, 5)
        # and the dBm value           
        self.ldbm.set_label('<span font_desc=\"Times 14 \" background=\"black\" foreground=\"yellow\"> -' + str(round(abs(meter_data[self.rx_meter_mode] + CORRECTION), 1)) + ' dBm     </span>')
        
    #---------------------------------------------------------------------      
    def clear_pixmap(self, widget, width, height):
        self.pixmap.draw_rectangle(widget.get_style().black_gc, True, 0, 0, width, height)